﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml;
using Common.Core;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace Common.Unity.Configuration
{
	public class CollectionValueElement : InjectionParameterValueElement, IDependencyResolverPolicy
	{
		readonly static InjectionParameterValueElementResolver resolver = new InjectionParameterValueElementResolver();
		IList list;

		[ConfigurationProperty( ElementTypeName, DefaultValue = null, IsRequired = false, IsKey = false )]
		public new string ElementType
		{
			get { return (string)this[ElementTypeName]; }
			set { this[ElementTypeName] = value; }
		}	const string ElementTypeName = "elementType";

		[ConfigurationProperty( CollectionTypeNameName, DefaultValue = null, IsRequired = false, IsKey = false )]
		public string CollectionTypeName
		{
			get { return (string)this[CollectionTypeNameName]; }
			set { this[CollectionTypeNameName] = value; }
		}	const string CollectionTypeNameName = "collectionType";

		[ConfigurationProperty( CollectionNameName, DefaultValue = null, IsRequired = false, IsKey = false )]
		public string CollectionName
		{
			get { return (string)this[CollectionNameName]; }
			set { this[CollectionNameName] = value; }
		}	const string CollectionNameName = "collectionName";

		public Type CollectionType
		{
			get { return TypeResolver.ResolveWithDefault( CollectionTypeName, typeof(ArrayList) ); }
		}

		[ConfigurationProperty( ItemTypeNameName, DefaultValue = null, IsRequired = false, IsKey = false )]
		public string ItemTypeName
		{
			get { return (string)this[ItemTypeNameName]; }
			set { this[ItemTypeNameName] = value; }
		}	const string ItemTypeNameName = "itemType";

		public Type ItemType
		{
			get { return TypeResolver.ResolveType( ItemTypeName ); }
		}

		List<InjectionParameterValueElement> Items
		{
			get { return items ?? ( items = new List<InjectionParameterValueElement>() ); }
		}	List<InjectionParameterValueElement> items;

		///<summary>
        ///Gets a value indicating whether an unknown element is encountered during deserialization.
        ///</summary>
        ///
        ///<returns>
        ///true when an unknown element is encountered while deserializing; otherwise, false.
        ///</returns>
        ///
        ///<param name="reader">The <see cref="T:System.Xml.XmlReader"></see> being used for deserialization.</param>
        ///<param name="elementName">The name of the unknown subelement.</param>
        ///<exception cref="T:System.Configuration.ConfigurationErrorsException">The element identified by elementName is locked.- or -One or more of the element's attributes is locked.- or -elementName is unrecognized, or the element has an unrecognized attribute.- or -The element has a Boolean attribute with an invalid value.- or -An attempt was made to deserialize a property more than once.- or -An attempt was made to deserialize a property that is not a valid member of the element.- or -The element cannot contain a CDATA or text element.</exception>
        protected override bool OnDeserializeUnrecognizedElement(
            string elementName, XmlReader reader)
        {
		 	InjectionParameterValueElement element = resolver.Resolve( elementName, reader );
			if ( element != null )
			{
				Items.Add( element );
				return true;
			}
			return base.OnDeserializeUnrecognizedElement(elementName, reader);
        }

		public override InjectionParameterValue CreateParameterValue(Type targetType)
		{
			return new DependencyResolverParameterValue( this, CollectionType );
		}

		object IDependencyResolverPolicy.Resolve( IBuilderContext context )
		{
			object result = list ?? ( list = ResolveList( context ) );
			return result;
		}

		IList ResolveList( IBuilderContext context )
		{
			IBuilderContext clone = context.CloneForNewBuild( new NamedTypeBuildKey( CollectionType, CollectionName ), null );
			IList result = clone.Strategies.ExecuteBuildUp( clone ) as IList;
			if ( result != null )
			{
				foreach ( InjectionParameterValueElement element in Items )
				{
					element.TypeResolver = TypeResolver;
					InjectionParameterValue value = element.CreateParameterValue( ItemType );
					IDependencyResolverPolicy policy = value.GetResolverPolicy( ItemType );
					object item = policy.Resolve( context );
					result.Add( item );
				}
				return result;
			}
			throw new InvalidOperationException( string.Format( "The specified type '{0}' does not implement IList.", CollectionType.FullName ) );
		}
	}
}
